package cn.iocoder.yudao.module.infrastructure.wechat.api;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.shop.request.shipping.*;
import cn.binarywang.wx.miniapp.bean.shop.response.WxMaOrderShippingInfoBaseResponse;
import cn.binarywang.wx.miniapp.bean.shop.response.WxMaOrderShippingInfoGetResponse;
import cn.hutool.core.date.TemporalAccessorUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.exception.KnownServiceException;
import cn.iocoder.yudao.module.api.infrastructure.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.api.infrastructure.social.enums.SocialTypeEnum;
import cn.iocoder.yudao.module.api.infrastructure.wechat.WechatClientApi;
import cn.iocoder.yudao.module.api.infrastructure.wechat.dto.WxaOrderNotifyConfirmReceiveDTO;
import cn.iocoder.yudao.module.api.infrastructure.wechat.dto.WxaUploadOrderShippingInfoDTO;
import cn.iocoder.yudao.module.api.infrastructure.wechat.vo.WechatOrderDetailVO;
import cn.iocoder.yudao.module.api.infrastructure.wechat.vo.WechatUserCheckBindVO;
import cn.iocoder.yudao.module.api.pay.wechat.enums.WechatOrderStatusEnum;
import cn.iocoder.yudao.module.infrastructure.social.api.SocialClientApiImpl;
import cn.iocoder.yudao.module.infrastructure.social.service.SocialUserService;
import cn.iocoder.yudao.module.infrastructure.wechat.service.WechatClientService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import java.time.DateTimeException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static cn.iocoder.yudao.module.api.infrastructure.wechat.dto.WxaUploadOrderShippingInfoDTO.LOGISTICS_TYPE_EXPRESS;

/**
 * @see SocialClientApiImpl
 */
@Slf4j
@Service
@Validated
public class WechatClientApiImpl implements WechatClientApi {
    @Resource
    private WechatClientService wechatClientService;
    @Resource
    private SocialUserService socialUserService;

    private final Lock lock = new ReentrantLock();

    @Override
    public String getWxMaAppId(Integer userType) {
        return wechatClientService.getWxMaService(UserTypeEnum.valueOf(userType)).getWxMaConfig().getAppid();
    }

    @Override
    public String getWxMpAppId(Integer userType) {
        return wechatClientService.getWxMpService(UserTypeEnum.valueOf(userType)).getWxMpConfigStorage().getAppId();
    }

    @Override
    public String getWxMaOpenId(String code) {
        try {
            WxMaJscode2SessionResult sessionResult = wechatClientService.getWxMaService(UserTypeEnum.MEMBER).jsCode2SessionInfo(code);
            return sessionResult.getOpenid();
        } catch (WxErrorException e) {
            log.error("获取微信小程序 openid 出错：{}", e.getError());
            throw new KnownServiceException(e.getError().getErrorMsg());
        }
    }

    @Override
    public WechatUserCheckBindVO checkWxMaUserExists(String code) {
        WechatUserCheckBindVO result = new WechatUserCheckBindVO();
        result.setBind(false);
        result.setOpenid("");
        try {
            String state = UUID.fastUUID().toString(true);
            SocialUserRespDTO socialUserRespDTO = socialUserService.getSocialUserByCode(UserTypeEnum.MEMBER.getValue(), SocialTypeEnum.WECHAT_MINI_PROGRAM.getType(), code, state);
            if (socialUserRespDTO != null) {
                result.setBind(socialUserRespDTO.getUserId() != null && socialUserRespDTO.getUserId() > 0);
                result.setOpenid(socialUserRespDTO.getOpenid());
            }
        } catch (Exception ignore) {
        }
        return result;
    }

    @Override
    public WechatUserCheckBindVO checkWxMpUserExists(String code) {
        WechatUserCheckBindVO result = new WechatUserCheckBindVO();
        result.setBind(false);
        result.setOpenid("");
        try {
            String state = UUID.fastUUID().toString(true);
            SocialUserRespDTO socialUserRespDTO = socialUserService.getSocialUserByCode(UserTypeEnum.MEMBER.getValue(), SocialTypeEnum.WECHAT_MP.getType(), code, state);
            if (socialUserRespDTO != null) {
                result.setBind(socialUserRespDTO.getUserId() != null && socialUserRespDTO.getUserId() > 0);
                result.setOpenid(socialUserRespDTO.getOpenid());
            }
        } catch (Exception ignore) {
        }
        return result;
    }

    @Override
    public String getWxMpOpenId(String code) {
        try {
            WxOAuth2AccessToken accessToken = wechatClientService.getWxMpService(UserTypeEnum.MEMBER).getOAuth2Service().getAccessToken(code);
            return accessToken.getOpenId();
        } catch (WxErrorException e) {
            log.error("获取微信公众号 openid 出错：{}", e.getError());
            throw new KnownServiceException(e.getError().getErrorMsg());
        }
    }

    @Override
    public void uploadWxMaOrderShippingInfo(Integer userType, WxaUploadOrderShippingInfoDTO dto) {
        try {
            lock.lock();
            List<ShippingListBean> shippingList;
            if (Objects.equals(LOGISTICS_TYPE_EXPRESS, dto.getLogisticsType())) {
                // 物流配送
                shippingList = Collections.singletonList(
                        ShippingListBean.builder()
                                .trackingNo(dto.getLogisticsNo())
                                .expressCompany(dto.getExpressCompany())
                                .itemDesc(dto.getItemDesc())
                                .contact(ContactBean.builder()
                                        .receiverContact(maskPhoneNumberWithRegex(dto.getReceiverContact()))
                                        .build())
                                .build());
            } else {
                shippingList = Collections.singletonList(
                        ShippingListBean.builder().itemDesc(dto.getItemDesc()).build());
            }
            WxMaOrderShippingInfoUploadRequest request = WxMaOrderShippingInfoUploadRequest.builder()
                    .orderKey(OrderKeyBean.builder()
                            // 使用原支付交易对应的微信订单号
                            .orderNumberType(2)
                            .transactionId(dto.getTransactionId())
                            .build())
                    //1、实体物流配送采用快递公司进行实体物流配送形式 2、同城配送 3、虚拟商品，虚拟商品，例如话费充值，点卡等，无实体配送形式 4、到店自提
                    .logisticsType(dto.getLogisticsType())
                    //1、UNIFIED_DELIVERY（统一发货）2、SPLIT_DELIVERY（分拆发货）
                    .deliveryMode(1)
                    .shippingList(shippingList)
                    .payer(PayerBean.builder().openid(dto.getOpenid()).build())
                    .uploadTime(getUploadTimeString())
                    .build();
            WxMaService wxMaService = wechatClientService.getWxMaService(UserTypeEnum.valueOf(userType));
            WxMaOrderShippingInfoBaseResponse response = wxMaService.getWxMaOrderShippingService().upload(request);
            if (response.getErrCode() == 0) {
                log.debug("上传微信小程序发货信息 渠道订单号【{}】 成功", dto.getTransactionId());
            } else {
                log.error("上传微信小程序发货信息 渠道订单号【{}】 失败：{}", dto.getTransactionId(), response);
            }
        } catch (WxErrorException e) {
            log.error("上传微信小程序发货信息 渠道订单号【{}】 出错：{}", dto.getTransactionId(), e.getError());
        } finally {
            lock.unlock();
        }
    }

    @Override
    public WechatOrderDetailVO getWxMaOrderInfo(Integer userType, String transactionId) {
        WechatOrderDetailVO result = new WechatOrderDetailVO();
        result.setTransactionId(transactionId);
        try {
            lock.lock();
            WxMaService wxMaService = wechatClientService.getWxMaService(UserTypeEnum.valueOf(userType));
            WxMaOrderShippingInfoGetRequest request = WxMaOrderShippingInfoGetRequest.builder()
                    .transactionId(transactionId)
                    .build();
            WxMaOrderShippingInfoGetResponse response = wxMaService.getWxMaOrderShippingService().get(request);
            result.setErrorMsg(response.getErrMsg());
            result.setRawData(JSONUtil.toJsonStr(response));
            WxMaOrderShippingInfoBaseResponse.Order order = response.getOrder();
            if (order != null) {
                log.debug("[getWxMaOrderInfo][获取微信小程序支付单信息成功：transactionId({})]", transactionId);
                result.setPaidAmount(order.getPaidAmount());
                result.setOrderState(order.getOrderState());
            } else {
                log.error("[getWxMaOrderInfo][获取微信小程序支付单信息失败：transactionId({}) {}", transactionId, response.getErrMsg());
                result.setOrderState(WechatOrderStatusEnum.ERROR.getValue());
            }
        } catch (WxErrorException ex) {
            log.error("[getWxMaOrderInfo][获取微信小程序支付单信息失败：transactionId({})]", transactionId, ex);
            result.setErrorMsg(ex.getError().getErrorMsg());
            result.setOrderState(WechatOrderStatusEnum.ERROR.getValue());
        } finally {
            lock.unlock();
        }
        return result;
    }

    @Override
    public void notifyWxMaOrderConfirmReceive(Integer userType, WxaOrderNotifyConfirmReceiveDTO dto) {
        try {
            WxMaService wxMaService = wechatClientService.getWxMaService(UserTypeEnum.valueOf(userType));
            WxMaOrderShippingInfoNotifyConfirmRequest request = WxMaOrderShippingInfoNotifyConfirmRequest.builder()
                    .transactionId(dto.getTransactionId())
                    .receivedTime(toEpochSecond(dto.getReceivedTime()))
                    .build();
            WxMaOrderShippingInfoBaseResponse response = wxMaService.getWxMaOrderShippingService().notifyConfirmReceive(request);
            if (response.getErrCode() != 0) {
                log.error("[notifyWxMaOrderConfirmReceive][确认收货提醒到微信小程序失败：request({}) response({})]", request, response);
                throw new KnownServiceException(response.getErrMsg());
            }
            log.info("[notifyWxMaOrderConfirmReceive][确认收货提醒到微信小程序成功：request({}) response({})]", request, response);
        } catch (WxErrorException ex) {
            log.error("[notifyWxMaOrderConfirmReceive][确认收货提醒到微信小程序失败：dto({})]", dto, ex);
            throw new KnownServiceException(ex.getError().getErrorMsg());
        }
    }

    private String getUploadTimeString() {
        ZoneId zoneId = ZoneId.systemDefault();
        ZonedDateTime now = ZonedDateTime.now(zoneId);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
                .withZone(zoneId);
        return now.format(formatter);
    }

    /**
     * 给手机少号中间4为打星
     *
     * @param phoneNumber 手机号
     */
    private String maskPhoneNumberWithRegex(String phoneNumber) {
        if (phoneNumber == null || phoneNumber.length() < 7) {
            return phoneNumber;
        }
        Pattern pattern = Pattern.compile("^(\\d{3})\\d{4}(\\d{4})$");
        Matcher matcher = pattern.matcher(phoneNumber);
        if (matcher.find()) {
            return matcher.group(1) + "****" + matcher.group(2);
        }
        return phoneNumber;
    }

    /**
     * 将给定的 {@link LocalDateTime} 转换为自 Unix 纪元时间（1970-01-01T00:00:00Z）以来的秒数。
     *
     * @param sourceDateTime 需要转换的本地日期时间，不能为空
     * @return 自 1970-01-01T00:00:00Z 起的秒数（epoch second）
     * @throws NullPointerException 如果 {@code sourceDateTime} 为 {@code null}
     * @throws DateTimeException 如果转换过程中发生时间超出范围或其他时间处理异常
     */
    private Long toEpochSecond(LocalDateTime sourceDateTime) {
        return TemporalAccessorUtil.toInstant(sourceDateTime).getEpochSecond();
    }

}
